package de.rj.urwalking.data.survey;

import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;

import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Root;

import android.content.Context;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import de.rj.urwalking.R;
import de.rj.urwalking.config.Config;
import de.rj.urwalking.config.Constants;

/**
 * 
 * Base class for all Questions. Contains common attributes like id and text. Defines some methods that subclasses have
 * to implement.
 * 
 * @author rjj
 * 
 */
@Root(name = "urw_participants_questions_answered", strict = false)
public abstract class Question implements Comparable<Question>, Serializable {

	private static final long serialVersionUID = 1L;

	private static final String TAG = Config.TAG;

	private static HashMap<Integer, Scale> scales = new HashMap<Integer, Scale>();
	@Element(name = "refquestion")
	protected int id; // same id as in DB (primary key)
	protected int itemNumber; // # in its section
	protected String text; // the actual question
	protected int scaleId; // what type it is
	protected int screen; // which questions are presented together and in what order

	@ElementList(name = "all-answer-versions")
	protected ArrayList<Answer> answerVersions = new ArrayList<Answer>();
	protected Scale scale;
	protected ArrayList<ScalePoint> scalePoints;

	protected boolean notApplicable = false;
	protected transient OnAnswerChangedListener mCallback;

	private long startTime;

	// Container Activity must implement this interface
	public interface OnAnswerChangedListener {
		public void onAnswerChanged();
	}

	public Question(int id, int itemNumber, String text, int scaleId, int screen) {
		super();
		this.id = id;
		this.itemNumber = itemNumber;
		this.text = text;
		this.scaleId = scaleId;
		this.screen = screen;

		scale = getScale(scaleId);
		scalePoints = scale.getScalePoints();

	}

	public static void addScales(HashMap<Integer, Scale> scales) {

		Question.scales.putAll(scales);
	}

	public static Scale getScale(int scaleId) {
		return scales.get(scaleId);
	}

	public int getItemNumber() {
		return itemNumber;
	}

	public String getText() {
		return text;
	}

	public int getScaleId() {
		return scaleId;
	}

	public int getId() {
		return id;
	}

	public int getScreen() {
		return screen;
	}

	public Answer getAnswer() {
		if (answerVersions.size() > 0) {
			return answerVersions.get(answerVersions.size() - 1);
		}
		// if we don't have an answer yet, let's just return an empty one in
		// order not to break the subclasses' functionality
		return new Answer();
	}

	public ArrayList<Answer> getAnswerVersions() {
		return answerVersions;
	}

	public boolean isNotApplicable() {
		return notApplicable;
	}

	public void setNotApplicable(boolean notApplicable) {
		this.notApplicable = notApplicable;
	}

	public void setStartTime(long timestamp) {

		this.startTime = timestamp;
	}

	/**
	 * 
	 * Subclasses have to implement this method and return their own View.
	 * 
	 * @param context
	 * @param parent
	 * @return the View containing this Question's UI
	 */
	public abstract View buildView(Context context, ViewGroup parent);

	public void setCallback(OnAnswerChangedListener listener) {
		mCallback = listener;
	};

	public void updateAnswer(int intAnswer) {
		updateAnswer(intAnswer, Constants.EMPTY_STRING);
	}

	public void updateAnswer(String textAnswer) {
		updateAnswer(Constants.UNDEFINED, textAnswer);
	}

	public void updateAnswer(int intAnswer, String textAnswer) {

		Answer answer = new Answer();
		answer.setIntAnswer(intAnswer);
		answer.setTextAnswer(textAnswer);
		answer.setTimestamp(System.currentTimeMillis());
		answer.setStartTimestamp(startTime);
		answer.setBlnIsLatest(true);
		answerVersions.add(answer);
		this.changeIsLatest();
		// notify the enclosing activity
		mCallback.onAnswerChanged();
	}

	private void changeIsLatest() {
		if (answerVersions.size() > 1) {
			for (int i = 0; i < answerVersions.size() - 1; i++) {
				Answer a = (Answer) answerVersions.get(i);
				a.setBlnIsLatest(false);
				answerVersions.set(i, a);
			}
		}
	}

	/**
	 * @param view
	 */
	protected void setQuestionText(View questionView) {
		TextView questionText = (TextView) questionView.findViewById(R.id.questionText);
		questionText.setText(getText());
	}

	/**
	 * Usually, a Question can be answered. Currently, the only exception is a {@link HeaderQuestion}.
	 */
	public boolean canBeAnswered() {
		return true;
	}

	/**
	 * Subclasses have to implement this method, using their own definition of when a question is answered or not.
	 */
	public abstract boolean hasBeenAnswered();

	/**
	 * 
	 * Parses one row of the query result and creates the corresponding question.
	 * 
	 * @param resultSet
	 *            current row from the query result representing a single question
	 * @return a Question object (actually one of the subclasses of it, depending on type in "refscale")
	 * @throws SQLException
	 */
	public static Question createFromDB(ResultSet resultSet) throws SQLException {

		Question question = null;

		int id = resultSet.getInt("id");
		int ordinalNumber = resultSet.getInt("ordinalNumber");
		String text = resultSet.getString("text");
		int scaleId = resultSet.getInt("refscale");
		int screen = resultSet.getInt("screen");

		switch (resultSet.getInt("refscale")) {
		case Config.SCALE_LIKERT7:
		case Config.SCALE_LIKERT5_ZUSTIMMUNG:
		case Config.SCALE_INTENSITY:
			if (Config.USE_STAR_RATING) {
				question = new StarRatingQuestion(id, ordinalNumber, text, scaleId, screen);
			} else {
				question = new Likert7Question(id, ordinalNumber, text, scaleId, screen);
			}
			break;
		case Config.SCALE_LIKERT5:
			question = new Likert5Question(id, ordinalNumber, text, scaleId, screen);
			break;
		case Config.SCALE_YESNO:
		case Config.SCALE_YESNO2:
		case Config.SCALE_BINARY:
		case Config.SCALE_GENDER:
		case Config.SCALE_AGREEMENT:
			question = new VerticalRadioQuestion(id, ordinalNumber, text, scaleId, screen);
			break;
		case Config.SCALE_CATEGORY:
			question = new CategoryQuestion(id, ordinalNumber, text, scaleId, screen);
			break;
		case Config.SCALE_FREETEXT:
			question = new FreeTextQuestion(id, ordinalNumber, text, scaleId, screen);
			break;
		case Config.SCALE_NO_SCALE:
			question = new HeaderQuestion(id, ordinalNumber, text, scaleId, screen);
			break;
		case Config.SCALE_OBJECT_LOCATION:
			question = new ImageQuestion(id, ordinalNumber, text, scaleId, screen);
			break;
		case Config.SCALE_COUNTRIES:
			question = new DropDownQuestion(id, ordinalNumber, text, scaleId, screen);
			break;

		default:
			// this could only happen if someone messes with the database...
			Log.w(TAG, "unknown scale type " + resultSet.getInt("refscale"));
			question = new HeaderQuestion(id, ordinalNumber,
					"Fehler beim Erstellen der Frage! Fragetyp nicht implementiert.", scaleId, screen);

		}

		return question;

	}

	/**
	 * Two Question objects are sorted first by screen, then by itemNumber.
	 */

	public int compareTo(Question another) {

		if (screen < another.getScreen()) {
			return -1;
		} else if (screen > another.getScreen()) {
			return 1;
		} else if (screen == another.getScreen()) {

			if (itemNumber < another.getItemNumber()) {
				return -1;
			} else if (itemNumber > another.getItemNumber()) {
				return 1;
			}
		}

		return 0;
	}

}
